/**
 * 标签节点
 */
class HengNode {

  constructor(tagName) {

    this.tag = tagName;      //标签名称
    this.ns = null;          //标签的名称空间
    this.children = [];      //子节点
    this.real = null;        //渲染后绑定的真实节点
    this.props = {};         //数据属性

    //为非空标签节点初始化属性、样式、事件
    if (tagName !== "_") {
      this.attributes = {};   //属性，不包含样式和事件
      this.styles = {};       //style的object形式，注意key是小驼峰命名
      this.events = {};       //事件，注意事件名没有前缀on
    }
    
  }

  /**
   * 在声明式构建过程中，将节点的引用赋值给一个句柄对象的属性，在声明式构建完成之后，可以通过句柄对象的属性来访问这个节点
   * @param {*} handleObject 句柄对象
   * @param {String} propertyName 句柄对象的属性名
   * @returns {HengNode}
   */
  handle(handleObject, propertyName) {
    handleObject[propertyName] = this;
    return this;
  }

  /**
   * 获取HengNode的真实节点的Rect
   * @returns 
   */
  rect() {
    return this.real?.getBoundingClientRect();
  }

  /**
   * 追加子节点
   * @param {*} child 
   */
  appendChild(child) {

    if (child === null || child === undefined || child === "" || child === false) return this;
    
    if (child.constructor === HengNode) this.children.push(child);
    else this.children.push(child.toString());

    if (this.tag !== "_" && this.real) {
      if (child.constructor === HengNode) child.render(this.real);
      else this.real.appendChild(child.toString());
    }

    return this;

  }

  /**
 * 在构建过程中给节点配置id属性，并将该节点的引用赋值给一个句柄对象，之后可以通过句柄对象的属性访问该节点
 * @param {String} value id值
 * @param {Object} handleObject 句柄对象
 * @param {String} handlePropertyName 句柄对象的属性名，缺省情况取id的值
 * @returns 
 */
  _id(value, handleObject, handlePropertyName) {
    this.attr("id", value);
    if (handleObject) this.handle(handleObject, handlePropertyName || value);
    return this;
  }

  /**
   * 为当前tag节点配置属性，若tag节点已经完成了挂载，则会同时为真实节点配置属性
   * @param {String|Object} nameOrObject 属性名或属性对象
   * @param {*} value 属性值
   * @returns {HengNode}
   */
  attr(nameOrObject, value) {

    if (this.tag === "_") return this;

    if (nameOrObject.constructor === String) {
      this.attributes[nameOrObject] = value;
      if (this.real) this.real.setAttribute(nameOrObject, value);
    }

    else if (nameOrObject.constructor === Object) {
      for (let key in nameOrObject) this.attr(key, nameOrObject[key]);
    }

    return this;

  }

  /**
   * 移除HengNode的属性，若HengNode已经完成挂载，则同时移除真实节点的属性
   * @param {String} key 属性名
   * @returns {HengNode}
   */
  removeAttr(key) {

    if (this.tag === "_") {
      return this;
    } 

    else {
      delete this.attributes[key];
      if (this.real) this.real.removeAttribute(key);
      return this;
    }

  }

  /**
   * 为节点增加class
   * @param {String} className 
   */
  addClass(className) {

    if (this.tag === "_") return this;

    let classList = this.attributes["class"];
    if (!classList) {
      classList = className;
    } 

    else {
      let arr = classList.split(/\s+/);
      for (let i = arr.length - 1; i >= 0; i--) {
        if (arr[i] === className) arr.splice(i, 1);
      }
      arr.push(className);
      classList == arr.join(" ");
    }

    classList = classList.trim().replace(/\s{2,}/g, " ");
    this.attributes["class"] = classList;
    if (this.real) this.real.classList.add(className);

    return this;

  }

  /**
   * 为节点移除类名
   * @param {String} className 
   */
  removeClass(className) {

    if (this.tag === "_") return this;

    let classList = this.attributes["class"];
    if (!classList) return this;

    let arr = classList.split(/\s+/);
    for (let i = arr.length - 1; i >= 0; i--) {
      if (arr[i] === className) arr.splice(i, 1);
    }

    classList == arr.join(" ");
    classList = classList.trim().replace(/\s{2,}/g, " ");
    this.attributes["class"] = classList;
    if (this.real) this.real.classList.remove(className);

    return this;
  }

  /**
   * 为HengNode配置样式，若HengNode已经完成了挂载，则会同时为真实节点配置样式
   * @param {String|Object} nameOrObject 样式属性名或样式对象
   * @param {*} value 样式的值
   * @returns {HengNode} 返回了当前节点，可以链式操作
   */
  css(nameOrObject, value) {

    if (this.tag === "_") return;

    if (nameOrObject.constructor === String) {
      if (typeof (value) === "number") value = value + "px";
      this.styles[nameOrObject] = value;
      if (this.real) this.real.style[nameOrObject] = value;
    }

    else if (nameOrObject.constructor === Object) {
      for (let key in nameOrObject) this.css(key, nameOrObject[key]);
    }

    return this;

  }

  /**
   * 为tag节点绑定事件，若tag节点已经完成挂载，则会同时为真实节点绑定事件
   * @param {String} eventType 事件名，不加on前缀
   * @param {(event)=>void} handler 事件处理函数
   * @param {boolean|AddEventListenerOptions|undefined} options 事件选项
   * @returns {HengNode}
   */
  on(eventType, handler, options = false) {
    if (this.tag === "_") return this;
    if (!this.events[eventType]) this.events[eventType] = [];
    this.events[eventType].push(handler);
    if (this.real) this.real.addEventListener(eventType, handler, options);
    return this;
  }

  /**
   * 清空内容
   */
  clear() {
    this.children = [];
    if (this.real) this.real.innerHTML = "";
    return this;
  }

  /**
   * 设置HengNode的html内容
   * @param {String} content html内容
   */
  html(content) {
    this.children = [content];
    if (this.real) this.real.innerHTML = content;
    return this;
  }

  /**
   * 为子节点添加相同的类名
   * @param {String} className 
   */
  childClass(className) {
    this.children.forEach(child => child.addClass(className));
    return this;
  }

  /**
   * 移除子节点的类名
   * @param {String} className 
   * @returns 
   */
  removeChildClass(className) {
    this.children.forEach(child => child.removeClass(className));
    return this;
  }

  /**
   * 为子节点添加相同的属性
   * @param {String|Object} nameOrObj 
   * @param {*} value 
   */
  childAttr(nameOrObj, value) {
    this.children.forEach(child => child.attr(nameOrObj, value));
    return this;
  }

  /**
   * 移除子节点的attribute
   * @param {String} key 
   */
  removeChildAttr(key) {
    this.children.forEach(child => child.removeAttr(key));
  }

  /**
   * 为子节点配置样式
   * @param {*} name 
   * @param {*} value 
   * @returns 
   */
  childCss(name, value) {
    this.children.forEach(child => child.css(name, value));
    return this;
  }

  /**
   * 在其真实节点下查询dom节点
   * @param {String} selector 
   */
  querySelector(selector) {
    if (this.real) {
      let node = this.real.querySelector(selector);
      //如果没有找到，则尝试到shadowRoot中查找
      if (!node && this.real.shadowRoot) {
        node = this.real.shadowRoot.querySelector(selector);
      }
      return node;
    }
  }

  /**
   * 在其真实节点下查询dom节点
   * @param {String} selector 
   */
  querySelectorAll(selector) {
    if (this.real) {
      let nodes = this.real.querySelectorAll(selector);
      //如果没有找到，则尝试到shadowRoot中查找
      if ((!nodes || !nodes.length) && this.real.shadowRoot) {
        nodes = this.real.shadowRoot.querySelectorAll(selector);
      }
      return nodes;
    }
  }

  /**
   * 将当前tag节点渲染到真实的DOM树中
   * @param {HTMLElement} container 容器节点
   * @param {String} where 插入位置
   */
  render(container,where="beforeend") {

    let realNode;   //创建一个真实节点
    if (this.tag === "_") realNode = new DocumentFragment();  //创建文档碎片
    else if (this.ns) realNode = document.createElementNS(this.ns, this.tag); //创建带命名空间的标签函数
    else realNode = document.createElement(this.tag); //创建普通节点

    this.real = realNode;

    if (this.tag !== "_") {
      //将tag节点的属性映射到真实节点
      for (let key in this.attributes) {
        if (["selected", "checked", "readonly", "disabled"].includes(key)) {
          //selected,checked,readonly,disabled四种属性要特别处理
          if (this.attributes[key]) realNode.setAttribute(key, key);
          else realNode.removeAttribute(key);
        }
        else {
          realNode.setAttribute(key, this.attributes[key]);
        }
      }
      //将tag节点的样式映射到真实节点
      for (let key in this.styles) realNode.style[key] = this.styles[key];
      //将tag节点的事件映射到真实节点
      for (let key in this.events) {
        this.events[key].forEach(item => realNode.addEventListener(key, item))
      }
    }

    //将tag节点的子节点映射到真实节点的子节点
    for (let child of this.children) {
      //如果当前子节点是一个tag节点，则递归，否则当做文本内容追加到真实节点
      if (child.constructor === HengNode) child.render(realNode);
      else realNode.insertAdjacentHTML("beforeend", child);
    }

    //将真实节点插入到容器中
    container.insertAdjacentElement(where,realNode);

  }

}

/**
 * 其他未知函数都当做开箱即用的属性配置函数、样式配置函数、事件配置函数，
 * 属性配置函数以_开始（第一个_会被移除，其他_会被替换成-）
 * 事件配置函数的首字母大写
 * 样式配置函数的首字母小写
 * 注意这些函数在HengNode.prototyppe.__proto__上，以避免覆盖实例本身的函数和prototype上的函数
 * 开箱即用函数的搜索过程：HengNode自身->HengNode的原型->HengNode的原型的原型
 */
HengNode.prototype.__proto__ = new Proxy({}, {

  get(_, propertyName) {

    //如果第一个字母是下划线，则返回一个配置(或获取)属性的函数，属性名不含第一个下划线
    const first = propertyName[0];

    if (first === "_") {
      let attributeName = propertyName.substring(1);
      attributeName = attributeName.split("_").join("-");
      let func = function (value) {
        //有参数，则配置属性值
        if (value !== undefined && value !== null) {
          return this.attr(attributeName, value);
        }
        //没有参数，则获取真实节点的属性值
        else if (this.real) {
          return this.real.getAttribute(attributeName);
        }
      }
      return func;
    }

    const isUpperCase = propertyName[0].toLowerCase() !== propertyName[0];  //如果字母转换成小写后不等于自身，则该字母一定是一个大写字母
    //如果以大写字母开始，则返回一个配置事件的函数
    if (isUpperCase) {
      let func = function (handler, options = false) {
        return this.on(propertyName.toLowerCase(), handler, options);
      }
      return func;
    }

    //如果不是以大写字母开始，则返回一个配置(或获取)样式的函数
    return (
      function (value) {
        if (value !== undefined && value !== null) {
          return this.css(propertyName, value);
        }
        else if (this.real) {
          return getComputedStyle(this.real, null)[propertyName];
        }
      }
    );

  }

});

/**
 * html语义化构造器(Heng读"珩")
 */
const Heng = {

  /**
  * 将中划线形式的样式属性名转换成小驼峰形式的样式属性名
  * @param {String} cssName css样式名称
  * @returns {String}
  */
  getStyleName(cssName) {
    const arr = cssName.split("-");
    cssName = arr[0];
    for (let i = 1; i < arr.length; i++) {
      cssName += (arr[i].substring(0, 1).toUpperCase() + arr[i].substring(1));
    }
    return cssName;
  },

  /**
   * 为tag节点配置样式
   * @param {HengNode} hengNode 
   * @param {String|Object} value 
   */
  setStyle(hengNode, value) {

    if (!hengNode?.tag || hengNode.tag === "_") return;

    //如果是字符串形式的样式，则将其组装成对象形式
    if (value.constructor === String) {
      const arr = value.split(";");
      let css, cssName, cssValue, styleName;
      for (let item of arr) {
        if (item.trim() === "") continue;
        css = item.split(":");
        cssName = css[0].trim();
        cssValue = css[1].trim();
        styleName = Heng.getStyleName(cssName);
        hengNode.styles[styleName] = cssValue;
      }
    }

    //如果是对象形式的style，则直接组装
    else if (value.constructor === Object) {
      for (let key in value) hengNode.styles[key] = value[key];
    }

  },

  /**
   * 为tag节点配置属性
   * @param {*} hengNode 目标tag节点
   * @param {*} obj 属性对象
   */
  setAttribute(hengNode, obj) {

    if (!hengNode?.tag || hengNode.tag === "_") return;

    for (let key in obj) {

      let value = obj[key];

      //如果value是一个函数，则配置事件
      if (value.constructor === Function) {
        if (!hengNode.events[key])  hengNode.events[key] = [];
        hengNode.events[key].push(value);
      }

      //如果key是style，则配置样式
      else if (key === "style") {
        Heng.setStyle(hengNode, value);
      }

      //配置attribute
      else {
        hengNode.attributes[key] = obj[key];
      }

    }

  },

  /**
   * 定义一个标签函数
   * @param {String} tagName 标签名称
   * @param {String|undefined} namespace 名称空间，主要用于svg
   * @returns {(...args:any[])=>HengNode}
   */
  define(tagName,namespace) {

    const ignore_args = [null, undefined, "", false, NaN];   //构建过程中忽略的参数，注意没有忽略0
    const reg_id = /(?<=#)[a-zA-Z_\$][\w\$\-]*/g;            //匹配选择器中的id
    const reg_class = /(?<=\.)[a-zA-Z_\-\$][\w\$\-]*/g;      //匹配选择器中的class
    
    //创建标签函数
    let tagFunction = function (...args) {

      let hengNode = new HengNode(tagName);
      if(namespace) hengNode.ns = namespace;

      for (let arg of args) {

        //忽略的参数
        if (ignore_args.includes(arg))  continue;

        if (arg.constructor === Function) {

          //如果是一个无参的函数，则先运行函数，再将返回值追加到父节点
          if (arg.length === 0) {
            hengNode.appendChild(arg());
          }

          //如果函数有一个形参，则将创建一个追加子节点的函数，用该函数来填充形参
          else if (arg.length === 1) {
            arg(child => hengNode.appendChild(child));
          }

        }

        //如果是一个tag节点数组，则追加到children
        else if (arg.constructor === Array) {
          for (let item of arg) {
            if(!ignore_args.includes(item)){
              hengNode.appendChild(item);
            }
          }
        }

        //如果是一个json则为节点配置属性
        else if (arg.constructor === Object) {
          Heng.setAttribute(hengNode, arg);
        }

        //此处可以用正则表达式，如/#nodeid.class1.class2/,以#号开始的段将标记为id，以.开始的段将标记为class
        else if (arg.constructor === RegExp) {
          //从正则中提取文本
          let str = arg.toString();
          let end = str.lastIndexOf("/");
          str = str.substring(1, end);
          //从正则表达式中提取最后一个id
          let ids = str.match(reg_id);
          if (ids?.length) {
            let id = ids[ids.length - 1];
            Heng.setAttribute(hengNode, { id: id });
          }
          //从正则表达式中提取所有的类
          let classes = str.match(reg_class);
          if (classes?.length) {
            classes.forEach(item => hengNode.addClass(item));
          }
        }

        //如果是HengNode
        else {
          hengNode.appendChild(arg);
        }

      }

      return hengNode;

    }

    return tagFunction;

  },

  /**
   * 定义一个svg标签函数
   * @param {String} tagName 标签名称
   * @returns 
   */
  defineSvg(tagName){
    return this.define(tagName,"http://www.w3.org/2000/svg");
  },

  /**
   * 开箱即用的标签函数
   */
  tags: new Proxy({}, {
    get(_, tagName) {
      tagName = tagName === "_" ? tagName : tagName.split("_").join("-");
      return Heng.define(tagName);
    }
  }),

  /**
   * 开箱即用的svg标签函数
   */
  svgTags:new Proxy({},{
    get(_,tagName){
      tagName = tagName==="_"?tagName:tagName.split("_").join("-");
      return Heng.defineSvg(tagName);
    }
  }), 

  /**
   * 挂载tag节点
   * @param {HengNode} hengNode tag节点
   * @param {Node|HengNode} container 容器
   * @param {Boolean} clearContainer 挂载之前是否清空容器
   */
  render(hengNode, container, clearContainer = false) {

    //清空container的内容
    if (clearContainer) {
      if(container instanceof HengNode){
        container.children=[];
        if(container.real) container.real.innerHTML="";
      }else{
        container.innerHTML = "";
      }
    }

    //如果容器是一个HengNode，则将当前HengNode追加到容器的子节点中
    if(container instanceof HengNode){
      container.children.push(hengNode);
      //如果容器已经渲染过了，那么子节点也要渲染
      if(container.real) hengNode.render(container.real);
    }

    else{
      hengNode.render(container);
    }
    
  }

}

//将一个Element封装为一个HengNode，使其具备链式调用的能力
Object.defineProperty(Element.prototype, "$$", {
  get: function () {
    const tagName = this.tagName.toLowerCase();
    const hengNode = new HengNode(tagName);
    hengNode.real = this;
    return hengNode;
  }
});

/**
* 链接到一个Element，返回一个HengNode，注意这个HengNode是残缺的，只能用于链式调用
* @param {Element|String} element DOM节点或选择器
* @param {Node|String} parent 选择器的查找范围
* @param {Boolean} inShadowRoot 是否在webComponent的shadowRoot中查找
*/
function $$(element, parent = document, inShadowRoot = false) {

  //如果element是一个HengNode，则返回自身
  if (element instanceof HengNode) return element;

  //如果element是一个Element，则将其包装成一个HengNode
  if (element instanceof Element) {
    const tagName = element.tagName.toLowerCase();
    const hengNode = new HengNode(tagName);
    hengNode.real = element;
    return hengNode;
  }

  //如果element是一个选择器，则从parent中查找element，然后将该element包装成HengNode
  if (element.constructor === String) {

    let ele = null;

    //如果parent是一个选择器，则到document中查找parent
    if (parent.constructor === String) {
      parent = document.querySelector(parent);
      if (!parent) return null;
    }

    //如果parent是一个WebComponent,并且有可见的shadowRoot，则到shadowRoot中查找element
    if (parent.shadowRoot && inShadowRoot)  ele = parent.shadowRoot.querySelector(element);
    else ele = parent.querySelector(element);
    if (!ele) return null;

    const tagName = ele.tagName.toLowerCase();
    const hengNode = new HengNode(tagName);
    hengNode.real = ele;
    return hengNode;

  }

}

//工程化时，打开下面一行注释
//export { HengNode, Heng, $$ };
